﻿using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Cache;
using System.Text;
using System.Text.RegularExpressions;
using xxpl.DNS;
using xxpl.HTTP;
using xxpl.Utils;

namespace xxpl.GAE
{
    public class SimpleRequestHandler : IHttpRequestHandler
    {
        private const int RangeBlock = 500 * 1024;

        private static readonly Regex regex = new Regex(@"(,\s)[^;=]*=[^=;]*;");

        public void Execute(HttpListenerRequest request, HttpListenerResponse response)
        {
            RequestContext requestContext = RequestContext.Create(request);

            HttpWebResponse responseFromProxy = null;
            HttpWebRequest requestToProxy = null;
            try
            {
                while (true)
                {
                    requestToProxy = GetRequestToProxy(requestContext);

                    responseFromProxy = (HttpWebResponse)requestToProxy.GetResponse();

                    int status = GetResponseCode(responseFromProxy);

                    //if need range fetch
                    if (IsNeedRangeFetch(status, requestContext))
                    {
                        responseFromProxy.Close();
                        requestContext.RangeStart = 0;
                        Console.WriteLine("Range Fetch:{0}", requestContext.RawUrl);
                        continue;
                    }

                    bool isFetchEnd = UpdateRange(requestContext, responseFromProxy);

                    ProcessHeader(requestContext, response, responseFromProxy);

                    ProcessResponse(responseFromProxy, response);

                    Console.WriteLine("{0}-{1} {2} {3}ms", requestToProxy.Host, requestContext.RawUrl, GetResponseCode(responseFromProxy), (int)(DateTime.Now - requestContext.RequestStartDate).TotalMilliseconds);

                    if (isFetchEnd) break;
                }
            }
            catch (Exception hex)
            {
                //PrintWebException(request, hex);
                SendErrorPage(500, response);
            }
            finally
            {
                if (responseFromProxy != null)
                {
                    responseFromProxy.Close();
                }
                else
                {
                    if (requestToProxy != null)
                        requestToProxy.Abort();
                }
            }
        }

        private static bool UpdateRange(RequestContext requestContext, HttpWebResponse responseFromProxy)
        {
            if (!requestContext.RangeStart.HasValue || requestContext.OrignalRangeSpecified) return true;

            string contentRange = responseFromProxy.Headers[HttpResponseHeader.ContentRange];

            if (string.IsNullOrEmpty(contentRange)) return true;

            if (!requestContext.TotalRange.HasValue)
                requestContext.TotalRange = int.Parse(contentRange.Substring(contentRange.IndexOf('/') + 1));

            requestContext.RangeStart += RangeBlock;

            return requestContext.RangeStart >= requestContext.TotalRange;
        }

        private static bool IsNeedRangeFetch(int status, RequestContext requestContext)
        {
            return false;
            return (status == 509 || status == 510)
                   && "GET".Equals(requestContext.HttpMethod, StringComparison.CurrentCultureIgnoreCase)
                   && !requestContext.OrignalRangeSpecified
                   && !requestContext.RangeStart.HasValue;
        }

        private static void SendErrorPage(int status, HttpListenerResponse response)
        {
            response.StatusCode = status;
            response.Abort();
        }


        private static void ProcessHeader(RequestContext context, HttpListenerResponse response, HttpWebResponse responseFromProxy)
        {
            //if first part
            if (context.RangeStart.GetValueOrDefault() != 0) return;

            int status = GetResponseCode(responseFromProxy);

            int length = GetDataLength(responseFromProxy);

            WebHeaderCollection webHeaderCollection = new WebHeaderCollection();
            webHeaderCollection.Add(responseFromProxy.Headers);

            webHeaderCollection.Remove(HttpResponseHeader.ContentLength);
            webHeaderCollection.Remove(HttpResponseHeader.TransferEncoding);
            webHeaderCollection.Remove(HttpResponseHeader.SetCookie);
            webHeaderCollection.Remove("X-Length");
            webHeaderCollection.Remove("X-Status");

            response.Headers.Add(webHeaderCollection);

            string setCookies = responseFromProxy.Headers[HttpResponseHeader.SetCookie];
            if (!string.IsNullOrEmpty(setCookies))
            {
                MatchCollection cMatch = regex.Matches(setCookies);
                int index = 0;
                foreach (Match match in cMatch)
                {
                    response.Headers.Add(HttpResponseHeader.SetCookie, setCookies.Substring(index, match.Index - index).TrimStart(',', ' '));
                    index = match.Index;
                }
                response.Headers.Add(HttpResponseHeader.SetCookie, setCookies.Substring(index).TrimStart(',', ' '));
            }

            if (responseFromProxy.ContentType.IndexOf("text", StringComparison.CurrentCultureIgnoreCase) > -1)
            {
                response.Headers.Add("Content-Encoding", "gzip");
            }

            response.StatusCode = status;
            response.StatusDescription = responseFromProxy.StatusDescription;

            //use total range when range fetch
            if (length > 1 || context.TotalRange.HasValue)
                response.ContentLength64 = context.TotalRange.HasValue ? context.TotalRange.Value : length - 1;
        }

        private static void ProcessResponse(HttpWebResponse responseFromProxy, HttpListenerResponse response)
        {
            int length = GetDataLength(responseFromProxy);

            int count = 0;
            byte[] result = new byte[1024];

            using (Stream streamFromProxy = responseFromProxy.GetResponseStream())
            {
                XorEncryptor ep = new XorEncryptor { Key = (byte)streamFromProxy.ReadByte() };
                count++;

                while (count < length)
                {
                    int readed = streamFromProxy.Read(result, 0, length - count > result.Length ? result.Length : length - count);

                    if (readed == 0 || readed < 0)
                    {
                        break;
                    }

                    count += readed;
                    ep.Encrypt(result, 0, readed);
                    response.OutputStream.Write(result, 0, readed);
                }

                streamFromProxy.Close();
            }
        }
        private static int GetDataLength(HttpWebResponse responseFromProxy)
        {
            var length = (int)responseFromProxy.ContentLength;

            if (length <= 0 && !string.IsNullOrEmpty(responseFromProxy.GetResponseHeader("X-Length")))
            {
                length = int.Parse(responseFromProxy.GetResponseHeader("X-Length"));
            }

            return length;
        }

        private static int GetResponseCode(HttpWebResponse responseFromProxy)
        {
            if (!string.IsNullOrEmpty(responseFromProxy.GetResponseHeader("X-Status")))
            {
                return int.Parse(responseFromProxy.GetResponseHeader("X-Status"));
            }

            return 500;
        }

        private static HttpWebRequest GetRequestToProxy(RequestContext request)
        {
            var requestToProxy = (HttpWebRequest)WebRequest.Create("https" + DnsHelper.GetHost() + "/xpp2");
            requestToProxy.Host = "java2theworld.appspot.com";
            requestToProxy.Method = "POST";
            requestToProxy.AllowAutoRedirect = false;
            requestToProxy.Proxy = null;
            requestToProxy.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);

            if (request.RangeStart.HasValue)
            {
                int to = request.RangeStart.Value + RangeBlock;
                if (to >= request.TotalRange)
                    request.HeaderCollection.Set(HttpRequestHeader.Range, string.Format("bytes={0}-", request.RangeStart.Value));
                else request.HeaderCollection.Set(HttpRequestHeader.Range, string.Format("bytes={0}-{1}", request.RangeStart.Value, to - 1));
            }

            using (Stream requestStream = requestToProxy.GetRequestStream())
            {
                var gZipStream = new DataZipStream(requestStream, CompressionMode.Compress);
                gZipStream.WriteBlock(request.HeaderCollection.ToString());
                gZipStream.WriteBlock(request.HttpMethod);
                gZipStream.WriteBlock(request.RawUrl);
                gZipStream.WriteBlock(request.PostData);
                gZipStream.Flush();
                gZipStream.Close();
                requestStream.Close();
            }
            return requestToProxy;
        }
    }
}